package socket;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Server {

    /*
      java.net.ServerSocket
      运行在服务端，主要作用有两个:
      1.向系统申请服务端口，客户端可以通过该端口与服务端建立连接
      2.监听服务端口，一旦一个客户端连接了，就会立即建立一个Socket实例与该客户端交互
     */
    private ServerSocket serverSocket;
    //存放所有客户的输出流
    private Map<String,PrintWriter> allOut = new HashMap<>();
    public Server(){
        System.out.println("正在启动服务器");
                    /*
              创建ServerSocket对象时，会申请一个端口
              如果该端口被其他程序占用，则会抛出异常:
               java.net.BindException: Address already in use
              解决办法:
              1.杀死占用该端口的进程(通常发生在服务端启动两次导致)
              2.更换端口号
             */
        try {
            serverSocket = new ServerSocket(8088);
            System.out.println("服务器启动完毕");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * 该方法用于启动程序的执行
     */
    public void start(){

        try {
            while (true){
                System.out.println("等待客户端连接");
              /*
              Socket accept():
              用于接收客户端的连接，并返回一个Scoket实例与连接的客户端交互
              该方法是一个阻塞方法，调用后程序会"卡住"，直到一个客户端连接为止
             */
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端连接了!");
                ClientHandler handler = new ClientHandler(socket);
                Thread t = new Thread(handler);
                t.start();

            }
        } catch (IOException e) {
        }
    }


    public static void main(String[] args) {
        Server server = new Server();
        server.start();

    }

    private class ClientHandler implements Runnable{
        private Socket socket;
        private String host;//记录该客户端的IP地址
        private String nickName;//记录客户端昵称
        public ClientHandler(Socket socket){
            this.socket = socket;
            host=socket.getInetAddress().getHostAddress();
        }

        @Override
        public void run() {
            PrintWriter pw = null;
            try {
                //通过socket获取输入流用于读取客户端发送的消息
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8);
                BufferedReader br = new BufferedReader(isr);

                //通过socket获取输出流用于向客户端发送消息
                OutputStream out = socket.getOutputStream();
                OutputStreamWriter osw = new OutputStreamWriter(out,StandardCharsets.UTF_8);
                BufferedWriter bw = new BufferedWriter(osw);
                pw = new PrintWriter(bw,true);

                nickName = br.readLine();//读取客户端发送过来的昵称
                //向集合中添加元素需要添加同步机制,确保多个线程不能同时添加
                synchronized (allOut){
                   allOut.put(nickName,pw);//将该客户的昵称和输出流存入Map中
                }

                sendMessageToAll("上线了,当前在线人数:"+allOut.size());//通知所有客户端,该用户上线了

                String message;
                while ((message=br.readLine())!=null){
//                    System.out.println("客户端说:"+message);//服务端输出客户端发送过来的消息
//                    pw.println("客户端说:"+message);//将消息发送回客户端   `
                    if (message.startsWith("@")){//如果聊天内容以@开头则表示私聊
                        sendMessageToSome(message);//调用私聊方法
                    }else {//否则为群聊
                        sendMessageToAll("说:"+message);
                    }
                }
            } catch (IOException e) {
            }finally {
                //处理客户端下线后的操作,共三步
                synchronized (allOut){//从集合中删除元素需要同步机制,确保多个线程不能同时删除
                    allOut.remove(nickName);//将该客户端的输出流从共享集合中删除
                }

                sendMessageToAll("下线了,当前在线人数:"+allOut.size());//发送给所有客户端,通知他们当前在线人数是多少
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        public void sendMessageToAll(String message){
            System.out.println(nickName+"["+host+"]"+message);
            /*
                新循环for底层使用的是iterator迭代器,迭代器遍历的的时候,集合不能动态增删,否则报错
                这里做了互斥处理
             */
            synchronized (allOut){//群发时也要保证多个线程同步处理
                for (PrintWriter o : allOut.values()){
                    o.println(nickName+"["+host+"]"+message);
                }
            }
        }

        public void sendMessageToSome(String message){
            if (message.matches("@.+:.+")){
                String toNickName = message.substring(1,message.indexOf(":"));
                if (allOut.containsKey(toNickName)){
                    PrintWriter pw = allOut.get(toNickName);
                    String toMessage = message.substring(message.indexOf(":")+1);
                    pw.println(nickName+"悄悄对你说:"+toMessage);

                }else {
                    PrintWriter pw = allOut.get(nickName);
                    pw.println("该用户["+toNickName+"[不在线!");
                }

            }else {//格式不正确,通知当前客户端格式不对
               PrintWriter pw = allOut.get(nickName);//根据昵称获取其对应的输出流
                pw.println("私聊格式不正确!");
            }
        }
    }
}
